package com.conncui.common.concurrent;

import java.util.HashMap;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

public class ThreadExecutorBuilder {
    /**
     * constant var
     */
    private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
    // We want at least 2 threads and at most 4 threads in the core pool,
    // preferring to have 1 less than the CPU count to avoid saturating
    // the CPU with background work
    private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));
    private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
    private static final int KEEP_ALIVE_SECONDS = 30;
    private static final TimeUnit KEEP_ALIVE_TIME_UNIT = TimeUnit.SECONDS;

    private static final BlockingQueue<Runnable> sPoolWorkQueue =
            new LinkedBlockingQueue<Runnable>(128);

    private static final RejectedExecutionHandler defaultHandler = new ThreadPoolExecutor.AbortPolicy();


    /**
     * parameter
     */

    private int corePoolSize = CORE_POOL_SIZE;
    private int maximumPoolSize = MAXIMUM_POOL_SIZE;
    private long keepAliveTime = KEEP_ALIVE_SECONDS;
    private TimeUnit unit = KEEP_ALIVE_TIME_UNIT;
    private BlockingQueue<Runnable> workQueue = sPoolWorkQueue;
    private RejectedExecutionHandler handler = defaultHandler;
    private String threadNamePrefix;

    /**
     * static
     */

    private static HashMap<String, ThreadPoolExecutor> sThreadPoolExecutorHashMap = new HashMap<String, ThreadPoolExecutor>();


    public ThreadExecutorBuilder setCorePoolSize(int corePoolSize) {
        this.corePoolSize = corePoolSize;
        return this;
    }

    public ThreadExecutorBuilder setMaximumPoolSize(int maximumPoolSize) {
        this.maximumPoolSize = maximumPoolSize;
        return this;
    }

    public ThreadExecutorBuilder setKeepAliveTime(long keepAliveTime) {
        this.keepAliveTime = keepAliveTime;
        return this;
    }

    public ThreadExecutorBuilder setUnit(TimeUnit unit) {
        this.unit = unit;
        return this;
    }

    public ThreadExecutorBuilder setWorkQueue(BlockingQueue<Runnable> workQueue) {
        this.workQueue = workQueue;
        return this;
    }

    public ThreadExecutorBuilder setHandler(RejectedExecutionHandler handler) {
        this.handler = handler;
        return this;
    }

    public ThreadExecutorBuilder setThreadNamePrefix(String threadNamePrefix) {
        this.threadNamePrefix = threadNamePrefix;
        return this;
    }

    public ThreadPoolExecutor getExecutor() {
        if (null == this.threadNamePrefix || this.threadNamePrefix.isEmpty()) {
            throw new IllegalArgumentException("Thread Name is must be required");
        }
        ThreadPoolExecutor executor = sThreadPoolExecutorHashMap.get(threadNamePrefix);
        if (executor == null) {
            executor = build();
        }
        return executor;
    }

    public static String ThreadPoolExecutorParameter(String name) {

        final StringBuilder sb = new StringBuilder("");
        ThreadPoolExecutor executor = sThreadPoolExecutorHashMap.get(name);
        if (executor == null) {
            sb.append("not find " + name + " Executor");
        } else {
            sb.append("ThreadExecutorBuilder{");
            sb.append("corePoolSize=").append(executor.getCorePoolSize()).append('\n');
            sb.append(", maximumPoolSize=").append(executor.getMaximumPoolSize()).append('\n');
            sb.append(", keepAliveTime=").append(executor.getKeepAliveTime(TimeUnit.MILLISECONDS)).append('\n');
            sb.append(", workQueue=").append(executor.getQueue()).append('\n');
            sb.append(", handler=").append(executor.getRejectedExecutionHandler()).append('\n');
            sb.append(", threadNamePrefix='").append(name).append('\n');
            sb.append('}');
        }

        return sb.toString();
    }

    private ThreadPoolExecutor build() {
        ThreadPoolExecutor executor;
        synchronized (ThreadExecutorBuilder.class) {
            executor = sThreadPoolExecutorHashMap.get(threadNamePrefix);
            if (executor == null) {
                ThreadFactory threadFactory = new ThreadFactory() {
                    private AtomicInteger mAtomicInteger = new AtomicInteger(1);

                    @Override
                    public Thread newThread(Runnable r) {
                        Thread thread = new Thread(r);
                        thread.setName(threadNamePrefix + "-" + mAtomicInteger.getAndIncrement());
                        return thread;
                    }
                };
                executor = new ThreadPoolExecutor(
                        corePoolSize,
                        maximumPoolSize,
                        keepAliveTime,
                        unit,
                        workQueue,
                        threadFactory,
                        handler == null ? defaultHandler : handler
                );
                sThreadPoolExecutorHashMap.put(threadNamePrefix, executor);
            }
        }
        System.out.println(ThreadExecutorBuilder.ThreadPoolExecutorParameter(threadNamePrefix));
        return executor;
    }
}